%%%-------------------------------------------------------------------
%%% @author Administrator
%%% @copyright (C) 2021, <COMPANY>
%%% @doc
%%% @end
%%%-------------------------------------------------------------------
-module(time_wheel).

-behaviour(gen_server).

-export([start_link/0]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).

-export([plan/2]).

-define(SERVER, ?MODULE).

-define(WARNING(Format), logger:warning_msg(?MODULE,?LINE, Format, [])).

-record(state, {}).

-define(timer_wheel_tick, timer_wheel_tick).
-define(timer_wheel_plan, timer_wheel_plan).

%%%===================================================================
%%% Spawning and gen_server implementation
%%%===================================================================

start_link() ->
  gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).

init([]) ->
  tick(time_wheel:now()),
  {ok, #state{}}.

handle_call(_Request, _From, State = #state{}) ->
  {reply, ok, State}.

handle_cast(_Request, State = #state{}) ->
  {noreply, State}.

%% 需要添加try catch 以防进程崩溃
handle_info(Info, State = #state{}) ->
  do_handle_info(Info),
  {noreply, State}.

terminate(_Reason, _State = #state{}) ->
  ok.

code_change(_OldVsn, State = #state{}, _Extra) ->
  {ok, State}.

%%%===================================================================
%%% Internal functions
%%%===================================================================

do_handle_info({?timer_wheel_tick, LastTick})->
  work(LastTick);
do_handle_info({add_plan, Sec, Ref, PID, MFA})->
  add_plan(Sec, Ref, PID, MFA);
do_handle_info({cancel_plan, Sec, Ref})->
  cancel_plan({Sec, Ref});
do_handle_info({mfa, M, F, A})->
  apply(M,F,A);
do_handle_info(Info)->
  ?WARNING("~w unknow msg = ~w",[?MODULE,Info]),
  ok.

%% 可以加一个时间偏移  用于开发阶段 调时间
now() ->
  {M, S, _} = os:timestamp(),
  M * 1000000 + S.

tick(Sec) ->
  erlang:send_after(1000, self(), {?timer_wheel_tick, Sec}).

work(LastTick) when is_integer(LastTick) ->
  NowSec = time_wheel:now(),
  if LastTick >= NowSec -> %%时间被回调了
    tick(NowSec);
    true ->
      work(LastTick + 1, NowSec)
  end.

work(NowSec, NowSec) ->
  work2(NowSec),
  tick(NowSec);
work(LastTick, NowSec) ->%要循环一个时间段是因为不能保证一定是每隔一秒会tick一次
  work2(LastTick),
  work(LastTick + 1, NowSec).

work2(Sec) ->
  Plans = clear_plan(Sec),
  lists:foreach(
    fun({_Ref, PID, Type, F}) ->
      work3(PID, Type, F, Sec)
    end, Plans).

work3(PID, mfa, {M, F, A}, Sec) ->
  catch PID ! {mfa, M, F, [Sec | A]}.

%%定时器
plan(TarSec, MFA) when is_tuple(MFA) ->
  Ref = erlang:make_ref(),
  PID = erlang:self(),
  erlang:send(?MODULE, {add_plan, TarSec, Ref, PID, MFA}),
  {TarSec, Ref}.

%% 增加一个计划
%% return: timerKey = {TarSec, Ref}
add_plan(Sec, Ref, PID, MFA) when is_tuple(MFA) ->
  Now = time_wheel:now(),
  case Sec =< Now of
    true ->
      work3(PID, mfa, MFA, Sec);
    _ ->
      set_plan(Sec, [{Ref, PID, mfa, MFA} | get_plan(Sec)])
  end.

%% 设置一秒的安排的所有计划
set_plan(Sec, Plans) ->
  put({?timer_wheel_plan, Sec}, Plans).

%% 获取计划
get_plan(Sec) ->
  case erlang:get({?timer_wheel_plan, Sec}) of
    A when is_list(A) ->
      A;
    _ ->
      []
  end.

%% 清空某一秒安排的所有计划，并返回旧的计划
clear_plan(Sec) ->
  case erlang:erase({?timer_wheel_plan, Sec}) of
    A when is_list(A) ->
      A;
    _ ->
      []
  end.

%% 删除计划
cancel_plan({Sec, Ref}) ->
  Plans = get_plan(Sec),
  Plans2 = lists:keydelete(Ref, 1, Plans),
  set_plan(Sec, Plans2);
cancel_plan(Plan) ->
  ?WARNING("cancel_plan unknow plan = ~w",[Plan]),
  ignore.